package com.github.bryx.workflow.command.executor;

import com.github.bryx.workflow.command.StartWorkflowInstanceCommand;
import com.github.bryx.workflow.domain.*;
import com.github.bryx.workflow.domain.process.runtime.TaskObject;
import com.github.bryx.workflow.domain.process.runtime.TaskObjectAssignee;
import com.github.bryx.workflow.dto.runtime.CreateWorkflowInstanceActivityDto;
import com.github.bryx.workflow.dto.runtime.CreateWorkflowInstanceDto;
import com.github.bryx.workflow.event.UserTaskEnterEvent;
import com.github.bryx.workflow.event.WorkflowStartEvent;
import com.github.bryx.workflow.handler.WorkflowInstanceAware;
import com.github.bryx.workflow.service.WorkflowBuildTimeService;
import com.github.bryx.workflow.service.WorkflowRuntimeService;
import com.github.bryx.workflow.service.process.ProcessService;
import com.github.bryx.workflow.util.CollectionsUtil;
import com.github.bryx.workflow.util.WorkflowEngineUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * @Author jameswu
 * @Date 2021/6/10
 **/
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class StartWorkflowInstanceCommandExecutor extends CommandExecutor<String> {

    @Autowired
    WorkflowBuildTimeService workflowBuildTimeService;

    @Autowired
    WorkflowRuntimeService workflowRuntimeService;

    @Autowired
    ProcessService processService;

    @Override
    @Transactional
    public String run() {
        StartWorkflowInstanceCommand command = this.getCommand().as(StartWorkflowInstanceCommand.class);
        WorkflowDef workflowDef = workflowBuildTimeService.query().getWorkflowDefByProcessDefKey(this.getProcessDefKey());
        WorkflowDefRev latestEnabledWorkflowDefRev = workflowBuildTimeService.query().getLatestEnabledWorkflowDefRev(workflowDef.getId());
        workflowDef.setRev(latestEnabledWorkflowDefRev);
        final Map<String, WorkflowDefProcessConfig.UserTaskConfig> userTasksConfig = workflowDef.getRev().getProcessConfig().getUserTasks();

        // 1. 开启process流程
        // 2. 询问workflowInstanceInterceptor获取任务受理人
        final WorkflowInstance workflowInstance = WorkflowInstance.builder().creatorId(command.getExecutorId()).formData(command.getFormData()).build();

        String processId = processService.startProcess(workflowDef.getRev().getProcessDefId(),
                command.getExecutorId(), command.getFormData(), (tasks, processObject) -> {
                    Map<String, List<TaskObjectAssignee>> taskObjectAssigneesMap = Maps.newHashMap();
                    tasks.forEach(taskObject -> {
                        if (CollectionsUtil.isNotEmpty(command.getAssignUserIds()) && CollectionsUtil.isNotEmpty(command.getAssigneeGroupIds())){
                            taskObjectAssigneesMap.put(taskObject.getId(), WorkflowEngineUtil.createTaskObjectAssignees(command.getAssignUserIds(), command.getAssigneeGroupIds()));
                        }else{
                            userTasksConfig.get(taskObject.getDefinitionId()).setTaskDefId(taskObject.getDefinitionId());
                            taskObjectAssigneesMap.put(taskObject.getId(), Optional.ofNullable(this.getWorkflowInstanceAware().userTaskAssign(this.getProcessDefKey(), workflowInstance, null, userTasksConfig.get(taskObject.getDefinitionId())))
                                    .orElse(Lists.newArrayList()));
                        }
                    });
                    return taskObjectAssigneesMap;
                });
        // 持久化流程实例及任务
        String workflowInstanceId = workflowRuntimeService.manager().createWorkflowInstance(CreateWorkflowInstanceDto.builder()
                .creatorId(command.getExecutorId())
                .defId(workflowDef.getRev().getDefId())
                .defRevId(workflowDef.getRev().getId())
                .formData(command.getFormData())
                .parentWorkflowTaskInstanceId(command.getParentWorkflowTaskInstanceId())
                .workflowInstanceRelationType(command.getWorkflowInstanceRelation())
                .processId(processId).build());
        // 创建任务实例
        List<TaskObject> tasks = processService.getTasks(processId);
        List<WorkflowTaskInstance> workflowTaskInstances = this.getCommandExecutorHelper().createWorkflowTaskInstances(workflowInstanceId, workflowDef.getRev(), tasks);

        // 检测是否有创建timer的必要
        this.getCommandExecutorHelper().createWorkflowTimerJobsIfNecessary(workflowDef.getProcessDefKey(), workflowInstance, workflowTaskInstances, latestEnabledWorkflowDefRev.getProcessConfig(), this.getWorkflowInstanceAware());

        // 发送WorkflowStartEvent
        this.getWorkflowInstanceAware().handleEvent(workflowDef.getProcessDefKey(), WorkflowStartEvent.builder()
                .creatorId(command.getExecutorId())
                .workflowInstance(workflowRuntimeService.query().getWorkflowInstanceById(workflowInstanceId))
                .newWorkflowTaskInstances(workflowTaskInstances)
                .formData(command.getFormData())
                .build());

        // 发送UserTaskEnterEvent
        workflowTaskInstances.forEach(taskInstance->{
            this.getWorkflowInstanceAware().handleEvent(workflowDef.getProcessDefKey(), UserTaskEnterEvent.builder()
                    .workflowTaskInstance(taskInstance)
                    .formData(command.getFormData())
                    .build());
        });

        CreateWorkflowInstanceActivityDto createWorkflowInstanceActivityDto = CreateWorkflowInstanceActivityDto.builder()
                .operatorId(command.getExecutorId())
                .workflowInstanceId(workflowInstanceId)
                .operateName(WorkflowUserOperationType.START.getName())
                .workflowTaskName("开始")
                .build();
        createWorkflowInstanceActivityDto.extractActivityFormData("start", latestEnabledWorkflowDefRev.getProcessConfig(), command.getFormData());
        this.getCommandExecutorHelper().createActivity(createWorkflowInstanceActivityDto);

        return workflowInstanceId;
    }
}
